﻿using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;

namespace Foundation
{
    /// <summary>
    /// Базовый конвертер, позволяет указывать внутренний конвертер
    /// </summary>
    [ContentProperty("Converter")]
    public abstract class ChainConverter : IValueConverter
    {
        /// <summary>
        /// Наследник может переопределить это свойство, указав в нем тип значения, которое может быть возвращено из внутреннего конвертера
        /// Этот тип будет передан в метод Convert внутреннего конвертера в параметре targetType
        /// Если он возвращает null, то используется тип вызывающего метода
        /// По-умолчанию используется null
        /// </summary>
        public virtual Type ThisType
        {
            get { return null; }
        }

        /// <summary>
        /// Внутренний конвертер
        /// </summary>
        public IValueConverter Converter { get; set; }

        /// <summary>
        /// Реализация интерфейса IValueConverter. Здесь мы просто вызываем метод Convert наследника, либо, если задан внутренний конвертер сначала вызываем
        /// его преобразование, а затем собственное
        /// </summary>
        object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (Converter != null)
                value = Converter.Convert(value, ThisType ?? targetType, parameter, culture);
            return Convert(value, targetType, parameter, culture);
        }

        /// <summary>
        /// Реализация интерфейса IValueConverter. Здесь мы просто вызываем метод ConvertBack наследника, либо, если задан внутренний конвертер,то после 
        /// вызова собственного преобразование вызываем преобразование наследника
        /// </summary>
        object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (Converter == null) return ConvertBack(value, targetType, parameter, culture);
            //Наследнику мы должны передать тип значения, которое мы хотим далее передать во внутренний конвертер.Если внутренний конвертер
            //реализует класс ChainConverter, то мы можем определить этот тип из его свойства thisType.
            //иначе мы не знаем какой тип нужен внутреннему конвертеру и просто указываем, что ему нужен object
            Type tarType = null;
            var chainConverter = Converter as ChainConverter;
            if (chainConverter != null)
                tarType = chainConverter.ThisType;
            var thisResult = ConvertBack(value, tarType ?? targetType, parameter, culture);
            //Здесь преобразовываем из цели в свое значение
            return Converter.ConvertBack(thisResult, targetType, parameter, culture);
            //И теперь передаем это значение внутреннему конвертеру
        }

        /// <summary>
        /// Наследник должен переопределить этот метод, указав как он будет преобразовывать значение
        /// </summary>
        /// <param name="value">Значение источника</param>
        /// <param name="targetType">Тип, в который необходимо преобразовать значение, может быть null</param>
        /// <param name="parameter">Параметр, переданный из Binding</param>
        /// <param name="culture">Культура, переданная из Binding</param>
        /// <returns>Необходимо вернуть сконвертированное значение</returns>
        public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);

        /// <summary>
        /// Наследник должен переопределить этот метод, указав как он будет обратно преобразовывать значение
        /// </summary>
        /// <param name="value">Значение цели</param>
        /// <param name="targetType">Тип источника</param>
        /// <param name="parameter">Параметр, переданный из Binding</param>
        /// <param name="culture">Культура, переданная из Binding</param>
        /// <returns>Необходимо сделать обратное преобразование и вернуть значение</returns>
        public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
    }
}